var mxGenericChangeCodec = function(obj, variable)
{
	var codec = new mxObjectCodec(obj, ['model', 'previous'], ['cell']);
	codec.afterDecode = function(dec, node, obj)
	{
		if (obj.previous == null)
		{
			obj.previous = obj[variable];
		}
		return obj;
	}
	return codec;
};
mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));

mxCodecRegistry.register(function()
{
	return new mxObjectCodec(new mxGraph(), ['graphListeners', 'eventListeners', 'view', 'container', 'cellRenderer',
		'editor', 'selection', 'activeElement', 'focusHandler', 'blurHandler']);
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxGraphView());
	codec.encode = function(enc, view)
	{
		return this.encodeCell(enc, view, view.graph.getModel().getRoot());
	};
	codec.encodeCell = function(enc, view, cell)
	{
		var model = view.graph.getModel();
		var state = view.getState(cell);
		var childCount = model.getChildCount(cell);
		var parent = model.getParent(cell);
		var geo = view.graph.getCellGeometry(cell);
		var name = null;

		if (parent == model.getRoot())
		{
			name = 'layer';
		}
		else if (parent == null)
		{
			name = 'graph';
		}
		else if (model.isEdge(cell))
		{
			name = 'edge';
		}
		else if (childCount > 0 && geo != null)
		{
			name = 'group';
		}
		else if (model.isVertex(cell))
		{
			name = 'vertex';
		}

		if (name != null)
		{
			var node = enc.document.createElement(name);
			var lab = view.graph.getLabel(cell);

			if (lab != null)
			{
				node.setAttribute('label', view.graph.getLabel(cell));

				if (view.graph.isHtmlLabel(cell))
				{
					node.setAttribute('html', true);
				}
			}

			if (parent == null)
			{
				var bounds = view.getGraphBounds();

				if (bounds != null)
				{
					node.setAttribute('x', Math.round(bounds.x));
					node.setAttribute('y', Math.round(bounds.y));
					node.setAttribute('width', Math.round(bounds.width));
					node.setAttribute('height', Math.round(bounds.height));
				}
				node.setAttribute('scale', view.scale);
			}
			else if (state != null && geo != null)
			{
				for (var i in state.style)
				{
					var value = state.style[i];

					if (typeof (value) == 'function' && typeof (value) == 'object')
					{
						value = mxStyleRegistry.getName(value);
					}

					if (value != null && typeof (value) != 'function' && typeof (value) != 'object')
					{
						node.setAttribute(i, value);
					}
				}
				var abs = state.absolutePoints;

				if (abs != null && abs.length > 0)
				{
					var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);

					for (var i = 1; i < abs.length; i++)
					{
						pts += ' ' + Math.round(abs[i].x) + ',' + Math.round(abs[i].y);
					}
					node.setAttribute('points', pts);
				}
				else
				{
					node.setAttribute('x', Math.round(state.x));
					node.setAttribute('y', Math.round(state.y));
					node.setAttribute('width', Math.round(state.width));
					node.setAttribute('height', Math.round(state.height));
				}
				var offset = state.absoluteOffset;

				if (offset != null)
				{
					if (offset.x != 0)
					{
						node.setAttribute('dx', Math.round(offset.x));
					}

					if (offset.y != 0)
					{
						node.setAttribute('dy', Math.round(offset.y));
					}
				}
			}
		}

		for (var i = 0; i < childCount; i++)
		{
			node.appendChild(this.encodeCell(enc, view, model.getChildAt(cell, i)));
		}
		return node;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxStylesheet());
	codec.encode = function(enc, obj)
	{
		var node = enc.document.createElement(mxUtils.getFunctionName(obj.constructor));

		for (var i in obj.styles)
		{
			var style = obj.styles[i];
			var styleNode = enc.document.createElement('add');

			if (i != null)
			{
				styleNode.setAttribute('as', i);

				for (var j in style)
				{
					var entry = enc.document.createElement('add');
					entry.setAttribute('as', j);
					var type = typeof (style[j]);

					if (type == 'function')
					{
						var name = mxStyleRegistry.getName(style[j]);

						if (name != null)
						{
							entry.setAttribute('value', name);
						}
					}
					else if (type != 'object')
					{
						entry.setAttribute('value', style[j]);
					}
					styleNode.appendChild(entry);
				}

				if (styleNode.childNodes.length > 0)
				{
					node.appendChild(styleNode);
				}
			}
		}
		return node;
	};
	codec.decode = function(dec, node, into)
	{
		var obj = into || new this.template.constructor();
		var id = node.getAttribute('id');

		if (id != null)
		{
			dec.objects[id] = obj;
		}
		node = node.firstChild;

		while (node != null)
		{
			if (!this.processInclude(dec, node, obj) && node.nodeName == 'add')
			{
				var as = node.getAttribute('as');

				if (as != null)
				{
					var extend = node.getAttribute('extend');
					var style = (extend != null) ? mxUtils.clone(obj.styles[extend]) : null;

					if (style == null)
					{
						if (extend != null)
						{
							mxLog.warn('mxStylesheetCodec.decode: stylesheet ' + extend + ' not found to extend');
						}
						style = {};
					}
					var entry = node.firstChild;

					while (entry != null)
					{
						if (entry.nodeType == mxConstants.NODETYPE_ELEMENT)
						{
							var key = entry.getAttribute('as');

							if (entry.nodeName == 'add')
							{
								var text = mxUtils.getTextContent(entry);
								var value = null;

								if (text != null && text.length > 0)
								{
									value = mxUtils.eval(text);
								}
								else
								{
									value = entry.getAttribute('value');

									if (mxUtils.isNumeric(value))
									{
										value = parseFloat(value);
									}
								}

								if (value != null)
								{
									style[key] = value;
								}
							}
							else if (entry.nodeName == 'remove')
							{
								delete style[key];
							}
						}
						entry = entry.nextSibling;
					}
					obj.putCellStyle(as, style);
				}
			}
			node = node.nextSibling;
		}
		return obj;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxDefaultKeyHandler());
	codec.encode = function(enc, obj)
	{
		return null;
	};
	codec.decode = function(dec, node, into)
	{
		if (into != null)
		{
			var editor = into.editor;
			node = node.firstChild;

			while (node != null)
			{
				if (!this.processInclude(dec, node, into) && node.nodeName == 'add')
				{
					var as = node.getAttribute('as');
					var action = node.getAttribute('action');
					var control = node.getAttribute('control');
					into.bindAction(as, action, control);
				}
				node = node.nextSibling;
			}
		}
		return into;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxDefaultToolbar());
	codec.encode = function(enc, obj)
	{
		return null;
	};
	codec.decode = function(dec, node, into)
	{
		if (into != null)
		{
			var editor = into.editor;
			var model = editor.graph.getModel();
			node = node.firstChild;

			while (node != null)
			{
				if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
				{
					if (!this.processInclude(dec, node, into))
					{
						if (node.nodeName == 'separator')
						{
							into.addSeparator();
						}
						else if (node.nodeName == 'br')
						{
							into.toolbar.addBreak();
						}
						else if (node.nodeName == 'hr')
						{
							into.toolbar.addLine();
						}
						else if (node.nodeName == 'add')
						{
							var as = node.getAttribute('as');
							as = mxResources.get(as) || as;
							var icon = node.getAttribute('icon');
							var pressedIcon = node.getAttribute('pressedIcon');
							var action = node.getAttribute('action');
							var mode = node.getAttribute('mode');
							var template = node.getAttribute('template');

							if (action != null)
							{
								into.addItem(as, icon, action, pressedIcon);
							}
							else if (mode != null)
							{
								var funct = mxUtils.eval(mxUtils.getTextContent(node));
								into.addMode(as, icon, mode, pressedIcon, funct);
							}
							else if (template != null)
							{
								var cell = editor.templates[template];
								var style = node.getAttribute('style');

								if (style != null)
								{
									cell = cell.clone();
									cell.setStyle(style);
								}
								var insertFunction = null;
								var text = mxUtils.getTextContent(node);

								if (text != null)
								{
									insertFunction = mxUtils.eval(text);
								}
								into.addPrototype(as, icon, cell, pressedIcon, insertFunction);
							}
							else
							{
								var children = mxUtils.getChildNodes(node);

								if (children.length > 0)
								{
									if (icon == null)
									{
										var combo = into.addActionCombo(as);

										for (var i = 0; i < children.length; i++)
										{
											var child = children[i];

											if (child.nodeName == 'separator')
											{
												into.addOption(combo, '---');
											}
											else if (child.nodeName == 'add')
											{
												var lab = child.getAttribute('as');
												var act = child.getAttribute('action');
												into.addActionOption(combo, lab, act);
											}
										}
									}
									else
									{
										var select = null;
										var create = function()
										{
											var template = editor.templates[select.value];

											if (template != null)
											{
												var clone = template.clone();
												var style = select.options[select.selectedIndex].cellStyle;

												if (style != null)
												{
													clone.setStyle(style);
												}
												return clone;
											}
											else
											{
												mxLog.warn('Template ' + template + ' not found');
											}
											return null;
										}
										var img = into.addPrototype(as, icon, create);
										select = into.addCombo();

										mxEvent.addListener(select, 'change', function()
										{
											into.toolbar.selectMode(img, function(evt)
											{
												var pt = mxUtils.convertPoint(editor.graph.container, evt.clientX,
													evt.clientY);
												return editor.addVertex(null, funct(), pt.x, pt.y);
											});
											into.toolbar.noReset = false;
										});

										for (var i = 0; i < children.length; i++)
										{
											var child = children[i];

											if (child.nodeName == 'separator')
											{
												into.addOption(select, '---');
											}
											else if (child.nodeName == 'add')
											{
												var lab = child.getAttribute('as');
												var tmp = child.getAttribute('template');
												var option = into.addOption(select, lab, tmp || template);
												option.cellStyle = child.getAttribute('style');
											}
										}
									}
								}
							}
						}
					}
				}
				node = node.nextSibling;
			}
		}
		return into;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxDefaultPopupMenu());
	codec.encode = function(enc, obj)
	{
		return null;
	};
	codec.decode = function(dec, node, into)
	{
		var inc = node.getElementsByTagName('include')[0];

		if (inc != null)
		{
			this.processInclude(dec, inc, into);
		}
		else if (into != null)
		{
			into.config = node;
		}
		return into;
	};
	return codec;
}());

mxCodecRegistry.register(function()
{
	var codec = new mxObjectCodec(new mxEditor(), ['modified', 'lastSnapshot', 'ignoredChanges', 'undoManager',
		'graphContainer', 'toolbarContainer']);
	codec.afterDecode = function(dec, node, obj)
	{
		var defaultEdge = node.getAttribute('defaultEdge');

		if (defaultEdge != null)
		{
			node.removeAttribute('defaultEdge');
			obj.defaultEdge = obj.templates[defaultEdge];
		}
		var defaultGroup = node.getAttribute('defaultGroup');

		if (defaultGroup != null)
		{
			node.removeAttribute('defaultGroup');
			obj.defaultGroup = obj.templates[defaultGroup];
		}
		return obj;
	};
	codec.decodeChild = function(dec, child, obj)
	{
		if (child.nodeName == 'Array')
		{
			var role = child.getAttribute('as');

			if (role == 'templates')
			{
				this.decodeTemplates(dec, child, obj);
				return;
			}
		}
		else if (child.nodeName == 'ui')
		{
			this.decodeUi(dec, child, obj);
			return;
		}
		mxObjectCodec.prototype.decodeChild.apply(this, arguments);
	};
	codec.decodeUi = function(dec, node, editor)
	{
		var tmp = node.firstChild;

		while (tmp != null)
		{
			if (tmp.nodeName == 'add')
			{
				var as = tmp.getAttribute('as');
				var elt = tmp.getAttribute('element');
				var style = tmp.getAttribute('style');
				var element = null;

				if (elt != null)
				{
					element = document.getElementById(elt);

					if (element != null && style != null)
					{
						element.style.cssText += ';' + style;
					}
				}
				else
				{
					var x = parseInt(tmp.getAttribute('x'));
					var y = parseInt(tmp.getAttribute('y'));
					var width = tmp.getAttribute('width');
					var height = tmp.getAttribute('height');
					element = document.createElement('div');
					element.style.cssText = style;
					var wnd = new mxWindow(mxResources.get(as) || as, element, x, y, width, height, false, true);
					wnd.setVisible(true);
				}

				if (as == 'graph')
				{
					editor.setGraphContainer(element);
				}
				else if (as == 'toolbar')
				{
					editor.setToolbarContainer(element);
				}
				else if (as == 'title')
				{
					editor.setTitleContainer(element);
				}
				else if (as == 'status')
				{
					editor.setStatusContainer(element);
				}
				else if (as == 'map')
				{
					editor.setMapContainer(element);
				}
			}
			else if (tmp.nodeName == 'resource')
			{
				mxResources.add(tmp.getAttribute('basename'));
			}
			else if (tmp.nodeName == 'stylesheet')
			{
				mxClient.link('stylesheet', tmp.getAttribute('name'));
			}
			tmp = tmp.nextSibling;
		}
	};
	codec.decodeTemplates = function(dec, node, editor)
	{
		if (editor.templates == null)
		{
			editor.templates = {};
		}
		var children = mxUtils.getChildNodes(node);

		for (var j = 0; j < children.length; j++)
		{
			var name = children[j].getAttribute('as');
			var child = children[j].firstChild;

			while (child != null && child.nodeType != 1)
			{
				child = child.nextSibling;
			}

			if (child != null)
			{

				editor.templates[name] = dec.decodeCell(child);
			}
		}
	};
	return codec;
}());